# -*- coding: utf-8 -*-
"""
HTMLTestRunner 是一个用于生成 HTML 测试报告的工具，兼容 unittest 框架。
支持 Python 3.6+，基于经典 HTMLTestRunner 改进。
"""

import sys
import time
import unittest
import traceback
from datetime import datetime
from io import StringIO
from pathlib import Path

try:
    from HTMLParser import HTMLParser  # Python 2
except ImportError:
    from html.parser import HTMLParser  # Python 3

try:
    from functools import partial
except ImportError:
    from functools import partial

# 定义颜色常量（用于控制台输出）
COLOR_RED = '\033[91m'
COLOR_GREEN = '\033[92m'
COLOR_YELLOW = '\033[93m'
COLOR_RESET = '\033[0m'


class _TestResult(unittest.TestResult):
    """扩展 unittest.TestResult，记录更多测试细节"""

    def __init__(self, stream, descriptions, verbosity):
        super().__init__(stream, descriptions, verbosity)
        self.stream = stream
        self.descriptions = descriptions
        self.verbosity = verbosity
        self.start_time = None
        self.end_time = None
        self.testsRun = 0
        self.failures = []
        self.errors = []
        self.skipped = []
        self.expectedFailures = []
        self.unexpectedSuccesses = []

    def startTest(self, test):
        super().startTest(test)
        self.start_time = time.time()
        if self.verbosity > 1:
            desc = self.descriptions[test] if self.descriptions else str(test)
            self.stream.writeln(f"\n{COLOR_YELLOW}开始执行测试：{desc}{COLOR_RESET}")

    def stopTest(self, test):
        super().stopTest(test)
        self.end_time = time.time()
        duration = round(self.end_time - self.start_time, 2)
        if self.verbosity > 1:
            self.stream.writeln(f"测试耗时：{duration}s")

    def addSuccess(self, test):
        super().addSuccess(test)
        self.testsRun += 1
        if self.verbosity > 1:
            desc = self.descriptions[test] if self.descriptions else str(test)
            self.stream.writeln(f"{COLOR_GREEN}✓ 成功：{desc}{COLOR_RESET}")

    def addFailure(self, test, err):
        super().addFailure(test, err)
        self.testsRun += 1
        self.failures.append((test, self._exc_info_to_string(err, test)))
        if self.verbosity > 1:
            desc = self.descriptions[test] if self.descriptions else str(test)
            self.stream.writeln(f"{COLOR_RED}✗ 失败：{desc}{COLOR_RESET}")

    def addError(self, test, err):
        super().addError(test, err)
        self.testsRun += 1
        self.errors.append((test, self._exc_info_to_string(err, test)))
        if self.verbosity > 1:
            desc = self.descriptions[test] if self.descriptions else str(test)
            self.stream.writeln(f"{COLOR_YELLOW}⚠ 错误：{desc}{COLOR_RESET}")

    def addSkip(self, test, reason):
        super().addSkip(test, reason)
        self.testsRun += 1
        self.skipped.append((test, reason))
        if self.verbosity > 1:
            desc = self.descriptions[test] if self.descriptions else str(test)
            self.stream.writeln(f"{COLOR_YELLOW}↪ 跳过：{desc}（原因：{reason}）{COLOR_RESET}")

    def _exc_info_to_string(self, err, test):
        """将异常信息转换为字符串"""
        exctype, value, tb = err
        # 去除 traceback 中当前测试框架的路径（可选）
        tb_str = ''.join(traceback.format_exception(exctype, value, tb))
        return f"{exctype.__name__}: {value}\n\n{tb_str}"


class HTMLTestRunner:
    """生成 HTML 测试报告的核心类"""

    def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None,
                 report_name=None, output_path='reports', failfast=False, buffer=False):
        self.stream = stream
        self.verbosity = verbosity
        self.title = title or "自动化测试报告"
        self.description = description or "无描述"
        self.report_name = report_name or f"测试报告_{datetime.now().strftime('%Y%m%d%H%M%S')}"
        self.output_path = Path(output_path)
        self.failfast = failfast
        self.buffer = buffer
        self.result = None

        # 确保输出目录存在
        self.output_path.mkdir(parents=True, exist_ok=True)

    def run(self, test):
        """执行测试并生成报告"""
        # 初始化测试结果对象
        self.result = _TestResult(
            stream=self.stream,
            descriptions=self.getDescriptions(test),
            verbosity=self.verbosity
        )
        # 设置测试结果的 failfast 和 buffer 属性
        self.result.failfast = self.failfast
        self.result.buffer = self.buffer

        # 执行测试
        startTime = time.time()
        startTestRun = getattr(test, 'startTestRun', None)
        if startTestRun is not None:
            startTestRun()
        try:
            test(self.result)
        finally:
            stopTestRun = getattr(test, 'stopTestRun', None)
            if stopTestRun is not None:
                stopTestRun()

        # 计算总耗时
        stopTime = time.time()
        timeTaken = round(stopTime - startTime, 2)

        # 生成 HTML 报告
        self.generate_report(test, self.result, timeTaken)
        return self.result

    def getDescriptions(self, test):
        """获取测试用例的描述"""
        doc_first_line = test.shortDescription()
        return doc_first_line if self.descriptions else None

    def generate_report(self, test, result, time_taken):
        """生成 HTML 报告内容并写入文件"""
        # 统计测试结果
        total = result.testsRun
        failures = len(result.failures)
        errors = len(result.errors)
        skips = len(result.skipped)
        expected_failures = len(result.expectedFailures)
        unexpected_successes = len(result.unexpectedSuccesses)
        successes = total - failures - errors - skips

        # 构建 HTML 内容
        html_content = f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{self.title}</title>
    <style>
        body {{ font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }}
        .container {{ max-width: 1200px; margin: 0 auto; background-color: white; padding: 20px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
        h1 {{ color: #333; text-align: center; }}
        h2 {{ color: #666; border-bottom: 1px solid #eee; padding-bottom: 10px; }}
        .summary {{ margin: 20px 0; padding: 15px; background-color: #f9f9f9; border-radius: 5px; }}
        .summary-item {{ margin: 5px 0; }}
        .success {{ color: {COLOR_GREEN}; }}
        .failure {{ color: {COLOR_RED}; }}
        .error {{ color: {COLOR_YELLOW}; }}
        .skip {{ color: #888; }}
        .test-case {{ margin: 10px 0; padding: 10px; border-left: 3px solid #ddd; }}
        .test-case.success {{ border-left-color: {COLOR_GREEN}; background-color: #f0fff0; }}
        .test-case.failure {{ border-left-color: {COLOR_RED}; background-color: #fff0f0; }}
        .test-case.error {{ border-left-color: {COLOR_YELLOW}; background-color: #fff8e5; }}
        .test-case.skip {{ border-left-color: #888; background-color: #f8f8f8; }}
        .stack-trace {{ margin: 10px 0; padding: 10px; background-color: #f8f8f8; border-radius: 3px; font-family: monospace; white-space: pre-wrap; }}
    </style>
</head>
<body>
    <div class="container">
        <h1>{self.title}</h1>
        <h2>{self.description}</h2>

        <div class="summary">
            <p><strong>执行时间：</strong>{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
            <p><strong>总用例数：</strong>{total}</p>
            <p class="success"><strong>成功：</strong>{successes}</p>
            <p class="failure"><strong>失败：</strong>{failures}</p>
            <p class="error"><strong>错误：</strong>{errors}</p>
            <p class="skip"><strong>跳过：</strong>{skips}</p>
            <p><strong>总耗时：</strong>{time_taken} 秒</p>
        </div>

        <h2>测试详情</h2>
        {self._generate_test_cases(result)}
    </div>
</body>
</html>"""

        # 写入 HTML 文件
        report_path = self.output_path / f"{self.report_name}.html"
        with open(report_path, 'w', encoding='utf-8') as f:
            f.write(html_content)
        self.stream.writeln(f"\n{COLOR_GREEN}测试报告已生成：{report_path}{COLOR_RESET}")

    def _generate_test_cases(self, result):
        """生成测试用例详情的 HTML 片段"""
        cases = []
        # 处理成功用例
        for test in result.successes:
            cases.append(self._get_test_case_html(test, "success", "成功"))
        # 处理失败用例
        for test, err in result.failures:
            cases.append(self._get_test_case_html(test, "failure", "失败", err))
        # 处理错误用例
        for test, err in result.errors:
            cases.append(self._get_test_case_html(test, "error", "错误", err))
        # 处理跳过用例
        for test, reason in result.skipped:
            cases.append(self._get_test_case_html(test, "skip", "跳过", reason))
        return '\n'.join(cases)

    def _get_test_case_html(self, test, status, status_text, error_msg=None):
        """生成单个测试用例的 HTML 片段"""
        desc = self.getDescriptions(test) or str(test)
        stack_trace = f"<div class='stack-trace'>{error_msg}</div>" if error_msg else ""
        return f"""
        <div class="test-case {status}">
            <p><strong>用例名称：</strong>{desc}</p>
            <p><strong>状态：</strong><span class="{status}">{status_text}</span></p>
            {stack_trace}
        </div>
        """


# 兼容旧版 HTMLTestRunner 的 run 方法（可选）
def run(test, **kwargs):
    runner = HTMLTestRunner(**kwargs)
    return runner.run(test)